package space.edge.d.demo.qrcode.privatecode.decoder;

import space.edge.d.demo.qrcode.common.ErrorCorrectionLevel;

public final class Version {

    /**
     * Element i represents the raw version bits that specify version i + 7
     */
    private static final int[] VERSION_DECODE_INFO = {
            0x01F25, 0x0216D, 0x03E4A, 0x042DE, 0x05DFA,
            0x063B1, 0x07C94, 0x085BC, 0x09A99, 0x0A4D3,
            0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928,
            0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6,
            0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1,
            0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75,
            0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F,
            0x24B0B, 0x2542E, 0x26A64, 0x27541, 0x28C69
    };

    private static final Version[] VERSIONS = buildVersions();

    private final int versionNumber;
    private final int[] alignmentPatternCenters;
    private final ECBlocks[] ecBlocks;
    private final int totalCodewords;

    private Version(int versionNumber,
                    int[] alignmentPatternCenters,
                    ECBlocks... ecBlocks) {
        this.versionNumber = versionNumber;
        this.alignmentPatternCenters = alignmentPatternCenters;
        this.ecBlocks = ecBlocks;
        int total = 0;
        int ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
        ECB[] ecbArray = ecBlocks[0].getECBlocks();
        for (ECB ecBlock : ecbArray) {
            total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
        }
        this.totalCodewords = total;
    }

    public static Version getVersionForNumber(int versionNumber) {
        if (versionNumber < 1 || versionNumber > 1) {
            throw new IllegalArgumentException();
        }
        return VERSIONS[versionNumber - 1];
    }

    public int getVersionNumber() {
        return versionNumber;
    }

    public int[] getAlignmentPatternCenters() {
        return alignmentPatternCenters;
    }

    public int getTotalCodewords() {
        return totalCodewords;
    }

    public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
        return ecBlocks[ecLevel.ordinal()];
    }

    /**
     * ecCodewordsPerBlock 每块对应的纠错码字，单位为byte
     */
    public static final class ECBlocks {
        private final int ecCodewordsPerBlock;
        private final ECB[] ecBlocks;

        ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) {
            this.ecCodewordsPerBlock = ecCodewordsPerBlock;
            this.ecBlocks = ecBlocks;
        }

        public int getECCodewordsPerBlock() {
            return ecCodewordsPerBlock;
        }

        public int getNumBlocks() {
            int total = 0;
            for (ECB ecBlock : ecBlocks) {
                total += ecBlock.getCount();
            }
            return total;
        }

        public int getTotalECCodewords() {
            return ecCodewordsPerBlock * getNumBlocks();
        }

        public ECB[] getECBlocks() {
            return ecBlocks;
        }
    }

    /**
     * count 码块个数
     * 码块中源码字的数量，单位为byte
     */
    public static final class ECB {
        private final int count;
        private final int dataCodewords;

        ECB(int count, int dataCodewords) {
            this.count = count;
            this.dataCodewords = dataCodewords;
        }

        public int getCount() {
            return count;
        }

        public int getDataCodewords() {
            return dataCodewords;
        }
    }

    @Override
    public String toString() {
        return String.valueOf(versionNumber);
    }

    /**
     * 每一个Version对应四个纠错级别的ECBlocks
     * 纠错级别对应的四个下标为
     * 0-L 1-M 2-Q 3-H
     */
    private static Version[] buildVersions() {
        return new Version[]{
                new Version(1, new int[]{6, 26},
                        new ECBlocks(20, new ECB(1, 80)),
                        new ECBlocks(18, new ECB(2, 32)),
                        new ECBlocks(26, new ECB(2, 24)),
                        new ECBlocks(16, new ECB(4, 9))),
        };
    }

    static Version decodeVersionInformation(int versionBits) {
        int bestDifference = Integer.MAX_VALUE;
        int bestVersion = 0;
        for (int i = 0; i < VERSIONS.length; i++) {
            int targetVersion = VERSION_DECODE_INFO[i];
            // Do the version info bits match exactly? done.
            if (targetVersion == versionBits) {
                return getVersionForNumber(i + 1);
            }
            // Otherwise see if this is the closest to a real version info bit string
            // we have seen so far
            int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
            if (bitsDifference < bestDifference) {
                bestVersion = i + 1;
                bestDifference = bitsDifference;
            }
        }
        // We can tolerate up to 3 bits of error since no two version info codewords will
        // differ in less than 8 bits.
        if (bestDifference <= 3) {
            return getVersionForNumber(bestVersion);
        }
        // If we didn't find a close enough match, fail
        return null;
    }

}
